//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using UnityEngine;

namespace Flare.Geom
{
    /// <summary>
    /// A 2D rectangle specified by its position (top-left corner), width, and height.
    /// </summary>
    public struct Rectangle
    {
        /// <summary>
        /// Access the x coordinate of the rectangle.
        /// </summary>
        public float x { get; set; }

        /// <summary>
        /// Access the y coordinate of the rectangle.
        /// </summary>
        public float y { get; set; }

        /// <summary>
        /// Access the width of the rectangle.
        /// </summary>
        public float width { get; set; }

        /// <summary>
        /// Access the height of the rectangle.
        /// </summary>
        public float height { get; set; }

        /// <summary>
        /// Returns the bottom coordinate of the rectangle (y + height).
        /// </summary>
        public float bottom
        {
            get { return this.y + this.height; }
        }

        /// <summary>
        /// Returns the left coordinate of the rectangle (x).
        /// </summary>
        public float left
        {
            get { return this.x; }
        }

        /// <summary>
        /// Returns the right coordinate of the rectangle (x + width).
        /// </summary>
        public float right
        {
            get { return this.x + this.width; }
        }

        /// <summary>
        /// Returns the top coordinate of the rectangle (y).
        /// </summary>
        public float top
        {
            get { return this.y; }
        }

        /// <summary>
        /// Returns the bottom right point of the rectangle (x + width, y + height).
        /// </summary>
        public Point bottomRight
        {
            get { return new Point(right, bottom); }
        }

        /// <summary>
        /// Returns the top left point of the rectangle (x, y).
        /// </summary>
        public Point topLeft
        {
            get { return new Point(left, top); }
        }

        /// <summary>
        /// Create a new rectangle using the given values.
        /// </summary>
        /// <param name="x">The x coordinate of the rectangle.</param>
        /// <param name="y">The y coordinate of the rectangle.</param>
        /// <param name="width">The width of the rectangle.</param>
        /// <param name="height">The height of the rectangle.</param>
        public Rectangle(float x = 0.0f, float y = 0.0f, float width = 0.0f, float height = 0.0f)
            : this()
        {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
        }

        /// <summary>
        /// Create a new rectangle by copying the values of the given rectangle.
        /// </summary>
        /// <param name="r">The rectangle to copy.</param>
        public Rectangle(Rectangle r)
            : this()
        {
            this.x = r.x;
            this.y = r.y;
            this.width = r.width;
            this.height = r.height;
        }

        /// <summary>
        /// Create a new rectangle as a clone of this rectangle.
        /// </summary>
        /// <returns>The new rectangle.</returns>
        public Rectangle Clone()
        {
            return new Rectangle(this);
        }

        /// <summary>
        /// Determines if the given point is contained within the rectangle.
        /// </summary>
        /// <param name="x">The x coordinate of the point.</param>
        /// <param name="y">The y coordinate of the point.</param>
        /// <returns>True if the point is within the rectangle.</returns>
        public bool Contains(float x, float y)
        {
            return (x >= left) && (x <= right) && (y >= top) && (y <= bottom);
        }

        /// <summary>
        /// Determines if the given point is contained within the rectangle.
        /// </summary>
        /// <param name="p">The point to test.</param>
        /// <returns>True if the point is within the rectangle.</returns>
        public bool ContainsPoint(Point p)
        {
            return Contains(p.x, p.y);
        }

        /// <summary>
        /// Copies values from the given rectangle into this rectangle.
        /// </summary>
        /// <param name="r">The rectangle to copy.</param>
        public void CopyFrom(Rectangle r)
        {
            this.x = r.x;
            this.y = r.y;
            this.width = r.width;
            this.height = r.height;
        }

        /// <summary>
        /// Determines if the rectangles are equal (i.e., they have the same x and y coordinates
        /// and the same width and height).
        /// </summary>
        /// <param name="r">The rectangle to compare.</param>
        /// <returns>True if the two rectangles are equal.</returns>
        public bool Equals(Rectangle r)
        {
            return (this.x == r.x) && (this.y == r.y) &&
                (this.width == r.width) && (this.height == r.height);
        }

        /// <summary>
        /// Inflate the rectangle by the specified amounts along the x axis and y axis. The
        /// rectangle's size increases to the left and right as well as to the top and
        /// bottom.
        /// </summary>
        /// <param name="dx">The x axis amount.</param>
        /// <param name="dy">The y axis amount.</param>
        public void Inflate(float dx, float dy)
        {
            this.x -= dx;
            this.width += 2 * dx;

            this.y -= dy;
            this.height += 2 * dy;
        }

        /// <summary>
        /// Determines if the rectangle is empty (i.e., its width or height is less than or
        /// equal to zero).
        /// </summary>
        /// <returns>True if the rectangle is empty.</returns>
        public bool IsEmpty()
        {
            return (this.width <= 0.0f) || (this.height <= 0.0f);
        }

        /// <summary>
        /// Adjust the position of the rectangle by the given amounts.
        /// </summary>
        /// <param name="dx">The x axis amount.</param>
        /// <param name="dy">The y axis amount.</param>
        public void Offset(float dx, float dy)
        {
            this.x += dx;
            this.y += dy;
        }

        /// <summary>
        /// Sets the properties of the rectangle to zero.
        /// </summary>
        public void SetEmpty()
        {
            this.x = 0.0f;
            this.y = 0.0f;
            this.width = 0.0f;
            this.height = 0.0f;
        }

        /// <summary>
        /// Sets the properties of the rectangle to the given values.
        /// </summary>
        public void SetTo(float x, float y, float width, float height)
        {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
        }

        /// <summary>
        /// Returns a formatted string representation of the rectangle.
        /// </summary>
        /// <returns>The rectangle represented as a string.</returns>
        public override string ToString ()
        {
             return string.Format("(x={0} y={1} w={2} h={3})",
                this.x, this.y, this.width, this.height);
        }

        /// <summary>
        /// An explicit operator to convert a rectangle into a Unity Rect.
        /// </summary>
        public static explicit operator UnityEngine.Rect(Rectangle rectangle)
        {
            Rect result = new Rect();

            result.x = rectangle.x;
            result.y = rectangle.y;
            result.width = rectangle.width;
            result.height = rectangle.height;

            return result;
        }
    }
}